package jsonrpc

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"reflect"
	"strconv"
)

// RPCClient 用于向提供JSON-RPC的后台发送基于HTTP协议的JSON-RPC请求.
type RPCClient interface {
	// Call 用于发送JSON-RPC请求到服务端.
	//
	// 规则说明, 参数只能是数组或map,不能是基础数据类型.
	// 所以有几个简单的规则要注意:
	//
	// 1. 无参数: Call("getinfo")
	//
	// 2. 单个基础数据参数: 参数值被封装在数组里面. 例如. Call("getByID", 1423)
	//
	// 3. 单个数组或map参数: 例如. Call("storePerson", &Person{Name: "Alex"})
	//
	// 4. 多个参数: 封装成数组. 例如. Call("setDetails", "Alex, 35, "Germany", true)
	//
	// 示例:
	//   Call("getinfo") -> {"method": "getinfo"}
	//   Call("getPersonId", 123) -> {"method": "getPersonId", "params": [123]}
	//   Call("setName", "Alex") -> {"method": "setName", "params": ["Alex"]}
	//   Call("setMale", true) -> {"method": "setMale", "params": [true]}
	//   Call("setNumbers", []int{1, 2, 3}) -> {"method": "setNumbers", "params": [1, 2, 3]}
	//   Call("setNumbers", 1, 2, 3) -> {"method": "setNumbers", "params": [1, 2, 3]}
	//   Call("savePerson", &Person{Name: "Alex", Age: 35}) -> {"method": "savePerson", "params": {"name": "Alex", "age": 35}}
	//   Call("setPersonDetails", "Alex", 35, "Germany") -> {"method": "setPersonDetails", "params": ["Alex", 35, "Germany"}}
	//
	Call(method string, params ...interface{}) (*RPCResponse, error)

	// CallRaw 和Call类似，但是没有处理requests.Params字段.
	// RPCRequest对象按提供的数据原样发送.
	// 查看: NewRequest, RPCRequest, Params()
	//
	// 建议优先考虑Call和CallFor
	CallRaw(request *RPCRequest) (*RPCResponse, error)

	// CallFor 一个非常方便的函数，可以向服务器端发送JSON-RPC请求，并直接指定一个对象来存储响应
	//
	// out: 请求成功时将存储解析json结果的对象

	// method和params: 和 Call() 方法的一样
	//
	// 如果请求不成功 (网络或http错误)或rpc响应返回一个错误,
	// error将得到一个错误类型的值. 如果它是JSON-RPC错误类型，那它可以通过*RPCError解析.
	//
	CallFor(out interface{}, method string, params ...interface{}) error

	// CallBatch 在一个请求中调用多个方法.
	//
	// 最直观的使用方式如下:
	// CallBatch(RPCRequests{
	//   NewRequest("myMethod1", 1, 2, 3),
	//   NewRequest("myMethod2", "Test"),
	// })
	//
	// 可以自己创建[]*RPCRequest数组，但不建议这样做，且应该注意以下几点:
	// - field Params is sent as provided, so Params: 2 forms an invalid json (correct would be Params: []int{2})
	// - you can use the helper function Params(1, 2, 3) to use the same format as in Call()
	// - `JSONRPC`字段值会被覆盖为"2.0"
	// - `ID`字段会被覆盖为一个映射到数组下标的值 (例如. requests[5].ID == 5)
	//
	// - 返回的RPCResponses是[]*RPCResponse
	// - 注意RPCResponses的值是无序的, 这样就可能发生: responses[i] != responses[i].ID
	// - RPCPersponses有一些有用的函数, 例如: responses.HasError()， 如果其中某一个请求响应是RPCError则返回true
	CallBatch(requests RPCRequests) (RPCResponses, error)

	// CallBatchRaw  在一个请求中调用多个RPCRequests.
	// It sends the RPCRequests parameter is it passed (no magic, no id autoincrement).
	//
	// 建议优先考虑CallBatch.
	//
	// CallBatchRaw(RPCRequests{
	//   &RPCRequest{
	//     ID: 123,            // 在CallBatchRaw里面，该值不会变
	//     JSONRPC: "wrong",   // 在CallBatchRaw里面，该值不会变
	//     Method: "myMethod1",
	//     Params: []int{1},   // 没有处理, 所以必须确保是array或map
	//   },
	//   &RPCRequest{
	//     ID: 612,
	//     JSONRPC: "2.0",
	//     Method: "myMethod2",
	//     Params: Params("Alex", 35, true), // Params()方法可以处理参数
	//   },
	// })
	//
	// - 返回的RPCResponses是[]*RPCResponse
	// - 注意RPCResponses的值是无序的
	// - 必须将id映射到您提供的id
	// - RPCPersponses有一些有用的函数, 例如: responses.HasError()， 如果其中某一个请求响应是RPCError则返回true
	CallBatchRaw(requests RPCRequests) (RPCResponses, error)
}

// RPCRequest 表示JSON-RPC请求对象.
//
// Method: 调用方法
//
// Params: 调用参数，可以是nil. 否则只能是object或map
//
// ID: 单个请求中值是1. 批量请求中每个请求的id值必须是唯一的.
//
// JSONRPC: JSON-RPC 2.0版本的值总是 "2.0"
//
// 大多数时候你不应该自己创建RPCRequest对象.而是使用 Call(), CallFor(), NewRequest()
//
// 如果要自己创建(例如CallRaw()), 组装参数考虑使用Params()函数.
//
// 例如,手动创建如下RPCRequest:
// request := &RPCRequest{
//   Method: "myMethod",
//   Params: Params("Alex", 35, true),
// }
//
// 如果知道自己在做什么，可以不用Params()以避免使用反射影响性能，但可能会创建不正确的rpc请求:
//request := &RPCRequest{
//   Method: "myMethod",
//   Params: 2, <-- 无效，因为单个基础数据类型的值必须包装在数组中 --> 没有使用Params()时
// }
//
// 正确方式:
// request := &RPCRequest{
//   Method: "myMethod",
//   Params: []int{2},
// }
type RPCRequest struct {
	Method  string      `json:"method"`
	Params  interface{} `json:"params,omitempty"`
	ID      int         `json:"id"`
	JSONRPC string      `json:"jsonrpc"`
}

// NewRequest 像使用Call()一样方便的返回一个RPCRequest
//
// e.g. NewRequest("myMethod", "Alex", 35, true)
func NewRequest(method string, params ...interface{}) *RPCRequest {
	request := &RPCRequest{
		Method:  method,
		Params:  Params(params...),
		JSONRPC: jsonrpcVersion,
	}

	return request
}

// RPCResponse 表示JSON-RPC响应对象
//
// Result: 如果没有发生错误，则保存rpc调用的结果，否则为nil。当然，成功的值也可能是nil。
// Error: 如果发生错误，则结果为RPCError对象, 成功则是nil。
// ID: 单个请求时的值是0. 批量请求中每个请求的id值必须是唯一的.
// JSONRPC: JSON-RPC 2.0版本的值总是 "2.0"
type RPCResponse struct {
	JSONRPC string      `json:"jsonrpc"`
	Result  interface{} `json:"result,omitempty"`
	Error   *RPCError   `json:"error,omitempty"`
	ID      interface{} `json:"id"`
}

// RPCError 表示JSON-RPC出错时错误对象
//
// Code: 错误码
// Message: 简短的错误信息
// Data: 额外的错误数据, 值可以是nil
type RPCError struct {
	Code    int         `json:"code"`
	Message string      `json:"message"`
	Data    interface{} `json:"data,omitempty"`
}

// Error function is provided to be used as error object.
func (e RPCError) Error() string {
	return strconv.Itoa(e.Code) + ":" + e.Message
}

// SetData 给RPCError的Data赋值
func (e RPCError) SetData(data interface{}) RPCError {
	e.Data = data
	return e
}

// HTTPError represents a error that occurred on HTTP level.
//
// An error of type HTTPError is returned when a HTTP error occurred (status code)
// and the body could not be parsed to a valid RPCResponse object that holds a RPCError.
//
// Otherwise a RPCResponse object is returned with a RPCError field that is not nil.
type HTTPError struct {
	Code int
	err  error
}

// Error function is provided to be used as error object.
func (e *HTTPError) Error() string {
	return e.err.Error()
}

type rpcClient struct {
	endpoint      string
	httpClient    *http.Client
	customHeaders map[string]string
}

// RPCClientOpts 用于给NewClientWithOpts()以更改RPCClient的配置.
//
// HTTPClient: 用于自定义http.Client (例如. 设置proxy,或 tls)
// CustomHeaders: 用于自定义headers, 例如. 设置 BasicAuth
type RPCClientOpts struct {
	HTTPClient    *http.Client
	CustomHeaders map[string]string
}

// RPCResponses 定义[]*RPCResponse的类型.
// 该类型实现了一些常用的处理列表值的方法
type RPCResponses []*RPCResponse

// AsMap RPCResponses改为id作key的map.id无效的会被抛弃，慎用！
func (res RPCResponses) AsMap() map[int]*RPCResponse {
	resMap := make(map[int]*RPCResponse, 0)
	for _, r := range res {
		if id, ok := r.ID.(int); ok {
			resMap[id] = r
		}
	}
	return resMap
}

// GetByID 根据给定的id获取RPCResponses的一个RPCResponse, 如果该id不存在则返回nil
func (res RPCResponses) GetByID(id int) *RPCResponse {
	for _, r := range res {
		if val, ok := r.ID.(int); ok {
			if val == id {
				return r
			}
		}
	}
	return nil
}

// HasError RPCResponses的任一RPCResponse错误则返回true
func (res RPCResponses) HasError() bool {
	for _, res := range res {
		if res.Error != nil {
			return true
		}
	}
	return false
}

// RPCRequests 定义[]*RPCRequest的类型
type RPCRequests []*RPCRequest

// NewClient 返回一个默认配置的RPCClient
//
// endpoint: 发送JSON-RPC请求的JSON-RPC服务端URL
func NewClient(endpoint string) RPCClient {
	return NewClientWithOpts(endpoint, nil)
}

// NewClientWithOpts 返回一个包含自定义配置的RPCClient实例
//
// endpoint: 发送JSON-RPC请求的JSON-RPC服务端URL
// opts: 自定义RPCClientOpts
func NewClientWithOpts(endpoint string, opts *RPCClientOpts) RPCClient {
	rpcClient := &rpcClient{
		endpoint:      endpoint,
		httpClient:    &http.Client{},
		customHeaders: make(map[string]string),
	}

	if opts == nil {
		return rpcClient
	}

	if opts.HTTPClient != nil {
		rpcClient.httpClient = opts.HTTPClient
	}

	if opts.CustomHeaders != nil {
		for k, v := range opts.CustomHeaders {
			rpcClient.customHeaders[k] = v
		}
	}

	return rpcClient
}

func (client *rpcClient) Call(method string, params ...interface{}) (*RPCResponse, error) {

	request := &RPCRequest{
		Method:  method,
		Params:  Params(params...),
		JSONRPC: jsonrpcVersion,
	}

	return client.doCall(request)
}

func (client *rpcClient) CallRaw(request *RPCRequest) (*RPCResponse, error) {

	return client.doCall(request)
}

func (client *rpcClient) CallFor(out interface{}, method string, params ...interface{}) error {
	rpcResponse, err := client.Call(method, params...)
	if err != nil {
		return err
	}

	if rpcResponse.Error != nil {
		return rpcResponse.Error
	}

	return rpcResponse.GetObject(out)
}

func (client *rpcClient) CallBatch(requests RPCRequests) (RPCResponses, error) {
	if len(requests) == 0 {
		return nil, errors.New("empty request list")
	}

	for i, req := range requests {
		req.ID = i
		req.JSONRPC = jsonrpcVersion
	}

	return client.doBatchCall(requests)
}

func (client *rpcClient) CallBatchRaw(requests RPCRequests) (RPCResponses, error) {
	if len(requests) == 0 {
		return nil, errors.New("empty request list")
	}

	return client.doBatchCall(requests)
}

func (client *rpcClient) newRequest(req interface{}) (*http.Request, error) {

	body, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}

	request, err := http.NewRequest("POST", client.endpoint, bytes.NewReader(body))
	if err != nil {
		return nil, err
	}

	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("Accept", "application/json")

	// set default headers first, so that even content type and accept can be overwritten
	for k, v := range client.customHeaders {
		request.Header.Set(k, v)
	}

	return request, nil
}

func (client *rpcClient) doCall(RPCRequest *RPCRequest) (*RPCResponse, error) {

	httpRequest, err := client.newRequest(RPCRequest)
	if err != nil {
		return nil, fmt.Errorf("rpc call %v() on %v: %v", RPCRequest.Method, client.endpoint, err.Error())
	}
	httpResponse, err := client.httpClient.Do(httpRequest)
	if err != nil {
		return nil, fmt.Errorf("rpc call %v() on %v: %v", RPCRequest.Method, httpRequest.URL.String(), err.Error())
	}
	defer httpResponse.Body.Close()

	var rpcResponse *RPCResponse
	decoder := json.NewDecoder(httpResponse.Body)
	decoder.DisallowUnknownFields()
	decoder.UseNumber()
	err = decoder.Decode(&rpcResponse)

	// parsing error
	if err != nil {
		// if we have some http error, return it
		if httpResponse.StatusCode >= 400 {
			return nil, &HTTPError{
				Code: httpResponse.StatusCode,
				err:  fmt.Errorf("rpc call %v() on %v status code: %v. could not decode body to rpc response: %v", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode, err.Error()),
			}
		}
		return nil, fmt.Errorf("rpc call %v() on %v status code: %v. could not decode body to rpc response: %v", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode, err.Error())
	}

	// response body empty
	if rpcResponse == nil {
		// if we have some http error, return it
		if httpResponse.StatusCode >= 400 {
			return nil, &HTTPError{
				Code: httpResponse.StatusCode,
				err:  fmt.Errorf("rpc call %v() on %v status code: %v. rpc response missing", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode),
			}
		}
		return nil, fmt.Errorf("rpc call %v() on %v status code: %v. rpc response missing", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode)
	}

	return rpcResponse, nil
}

func (client *rpcClient) doBatchCall(rpcRequest []*RPCRequest) ([]*RPCResponse, error) {
	httpRequest, err := client.newRequest(rpcRequest)
	if err != nil {
		// return nil, fmt.Errorf("rpc batch call on %v: %v", client.endpoint, err.Error())
		return nil, err
	}
	httpResponse, err := client.httpClient.Do(httpRequest)
	if err != nil {
		return nil, err
		// return nil, fmt.Errorf("rpc batch call on %v: %v", httpRequest.URL.String(), err.Error())
	}
	defer httpResponse.Body.Close()

	var rpcResponse RPCResponses
	decoder := json.NewDecoder(httpResponse.Body)
	decoder.DisallowUnknownFields()
	decoder.UseNumber()
	err = decoder.Decode(&rpcResponse)

	// parsing error
	if err != nil {
		// if we have some http error, return it
		if httpResponse.StatusCode >= 400 {
			return nil, &HTTPError{
				Code: httpResponse.StatusCode,
				err:  fmt.Errorf("rpc batch call on %v status code: %v. could not decode body to rpc response: %v", httpRequest.URL.String(), httpResponse.StatusCode, err.Error()),
			}
		}
		return nil, fmt.Errorf("rpc batch call on %v status code: %v. could not decode body to rpc response: %v", httpRequest.URL.String(), httpResponse.StatusCode, err.Error())
	}

	// response body empty
	if rpcResponse == nil || len(rpcResponse) == 0 {
		// if we have some http error, return it
		if httpResponse.StatusCode >= 400 {
			return nil, &HTTPError{
				Code: httpResponse.StatusCode,
				err:  fmt.Errorf("rpc batch call on %v status code: %v. rpc response missing", httpRequest.URL.String(), httpResponse.StatusCode),
			}
		}
		return nil, fmt.Errorf("rpc batch call on %v status code: %v. rpc response missing", httpRequest.URL.String(), httpResponse.StatusCode)
	}

	return rpcResponse, nil
}

// Params is a helper function that uses the same parameter syntax as Call().
// But you should consider to always use NewRequest() instead.
//
// e.g. to manually create an RPCRequest object:
// request := &RPCRequest{
//   Method: "myMethod",
//   Params: Params("Alex", 35, true),
// }
//
// same with new request:
// request := NewRequest("myMethod", "Alex", 35, true)
//
// If you know what you are doing you can omit the Params() call but potentially create incorrect rpc requests:
// request := &RPCRequest{
//   Method: "myMethod",
//   Params: 2, <-- invalid since a single primitive value must be wrapped in an array --> no magic without Params()
// }
//
// correct:
// request := &RPCRequest{
//   Method: "myMethod",
//   Params: []int{2}, <-- valid since a single primitive value must be wrapped in an array
// }
func Params(params ...interface{}) interface{} {
	var finalParams interface{}

	// if params was nil skip this and p stays nil
	if params != nil {
		switch len(params) {
		case 0: // no parameters were provided, do nothing so finalParam is nil and will be omitted
		case 1: // one param was provided, use it directly as is, or wrap primitive types in array
			if params[0] != nil {
				var typeOf reflect.Type

				// traverse until nil or not a pointer type
				for typeOf = reflect.TypeOf(params[0]); typeOf != nil && typeOf.Kind() == reflect.Ptr; typeOf = typeOf.Elem() {
				}

				if typeOf != nil {
					// now check if we can directly marshal the type or if it must be wrapped in an array
					switch typeOf.Kind() {
					// for these types we just do nothing, since value of p is already unwrapped from the array params
					case reflect.Struct:
						finalParams = params[0]
					case reflect.Array:
						finalParams = params[0]
					case reflect.Slice:
						finalParams = params[0]
					case reflect.Interface:
						finalParams = params[0]
					case reflect.Map:
						finalParams = params[0]
					default: // everything else must stay in an array (int, string, etc)
						finalParams = params
					}
				}
			} else {
				finalParams = params
			}
		default: // if more than one parameter was provided it should be treated as an array
			finalParams = params
		}
	}

	return finalParams
}

// GetInt 将rpc响应转换为一个int64
func (RPCResponse *RPCResponse) GetInt() (int64, error) {
	val, ok := RPCResponse.Result.(json.Number)
	if !ok {
		return 0, fmt.Errorf("could not parse int64 from %s", RPCResponse.Result)
	}

	i, err := val.Int64()
	if err != nil {
		return 0, err
	}

	return i, nil
}

// GetFloat 将rpc响应转换为一个float64
func (RPCResponse *RPCResponse) GetFloat() (float64, error) {
	val, ok := RPCResponse.Result.(json.Number)
	if !ok {
		return 0, fmt.Errorf("could not parse float64 from %s", RPCResponse.Result)
	}

	f, err := val.Float64()
	if err != nil {
		return 0, err
	}

	return f, nil
}

// GetBool 将rpc响应转换为一个bool
func (RPCResponse *RPCResponse) GetBool() (bool, error) {
	val, ok := RPCResponse.Result.(bool)
	if !ok {
		return false, fmt.Errorf("could not parse bool from %s", RPCResponse.Result)
	}

	return val, nil
}

// GetString 将rpc响应转换为一个string
func (RPCResponse *RPCResponse) GetString() (string, error) {
	val, ok := RPCResponse.Result.(string)
	if !ok {
		return "", fmt.Errorf("could not parse string from %s", RPCResponse.Result)
	}

	return val, nil
}

// GetObject 将rpc响应转换为任意类型(结果即json.Unmarshal()中所接收的值)
func (RPCResponse *RPCResponse) GetObject(toType interface{}) error {
	js, err := json.Marshal(RPCResponse.Result)
	if err != nil {
		return err
	}
	err = json.Unmarshal(js, toType)
	if err != nil {
		return err
	}

	return nil
}
